package com.bdth.weget.net.codec;
import com.bdth.weget.util.BinaryUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException;

import java.nio.ByteOrder;
import java.util.List;

/**
 * Created by Administrator on 2018/4/18.
 */
public class ProtocolDecoder extends ByteToMessageDecoder {

    private final ByteOrder byteOrder;
    private final int maxFrameLength;
    private final int lengthFieldOffset;
    private final int lengthFieldLength;
    private final int lengthFieldEndOffset;
    private final int lengthAdjustment;
    private final int initialBytesToStrip;
    private final boolean failFast;
    private boolean discardingTooLongFrame;
    private long tooLongFrameLength;
    private long bytesToDiscard;

    public ProtocolDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
        this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
    }

    public ProtocolDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
        this(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, true);
    }

    public ProtocolDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        this(ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    }

    public ProtocolDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        if(byteOrder == null) {
            throw new NullPointerException("byteOrder");
        } else if(maxFrameLength <= 0) {
            throw new IllegalArgumentException("maxFrameLength must be a positive integer: " + maxFrameLength);
        } else if(lengthFieldOffset < 0) {
            throw new IllegalArgumentException("lengthFieldOffset must be a non-negative integer: " + lengthFieldOffset);
        } else if(initialBytesToStrip < 0) {
            throw new IllegalArgumentException("initialBytesToStrip must be a non-negative integer: " + initialBytesToStrip);
        } else if(lengthFieldOffset > maxFrameLength - lengthFieldLength) {
            throw new IllegalArgumentException("maxFrameLength (" + maxFrameLength + ") " + "must be equal to or greater than " + "lengthFieldOffset (" + lengthFieldOffset + ") + " + "lengthFieldLength (" + lengthFieldLength + ").");
        } else {
            this.byteOrder = byteOrder;
            this.maxFrameLength = maxFrameLength;
            this.lengthFieldOffset = lengthFieldOffset;
            this.lengthFieldLength = lengthFieldLength;
            this.lengthAdjustment = lengthAdjustment;
            this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
            this.initialBytesToStrip = initialBytesToStrip;
            this.failFast = failFast;
        }
    }

    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = this.decode(ctx, in);
        if(decoded != null) {
            out.add(decoded);
        }

    }

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //转义字符转换回来
        int length = in.readableBytes();
        byte[] message = new byte[length];
        in.readBytes(message);
        //System.out.println(BinaryUtil.ToHexFormatString(message));
        byte[] result = BinaryUtil.UnEscape(message);
        //System.out.println(BinaryUtil.ToHexFormatString(result));
        ByteBuf frameByte = Unpooled.directBuffer(16);
        frameByte.writeByte(0x7e);
        frameByte.writeBytes(result);
        frameByte.writeByte(0x7e);

        if(this.discardingTooLongFrame) {
            long actualLengthFieldOffset = this.bytesToDiscard;
            int localBytesToDiscard = (int)Math.min(actualLengthFieldOffset, (long)frameByte.readableBytes());
            frameByte.skipBytes(localBytesToDiscard);
            actualLengthFieldOffset -= (long)localBytesToDiscard;
            this.bytesToDiscard = actualLengthFieldOffset;
            this.failIfNecessary(false);
        }

        if(frameByte.readableBytes() < this.lengthFieldEndOffset) {
            return null;
        } else {
            int actualLengthFieldOffset1 = frameByte.readerIndex() + this.lengthFieldOffset;
            long frameLength = this.getUnadjustedFrameLength(frameByte, actualLengthFieldOffset1, this.lengthFieldLength, this.byteOrder);
            if(frameLength < 0L) {
                frameByte.skipBytes(this.lengthFieldEndOffset);
                throw new CorruptedFrameException("negative pre-adjustment length field: " + frameLength);
            } else {
                frameLength += (long)(this.lengthAdjustment + this.lengthFieldEndOffset);
                if(frameLength < (long)this.lengthFieldEndOffset) {
                    frameByte.skipBytes(this.lengthFieldEndOffset);
                    throw new CorruptedFrameException("Adjusted frame length (" + frameLength + ") is less " + "than lengthFieldEndOffset: " + this.lengthFieldEndOffset);
                } else if(frameLength > (long)this.maxFrameLength) {
                    long frameLengthInt1 = frameLength - (long)frameByte.readableBytes();
                    this.tooLongFrameLength = frameLength;
                    if(frameLengthInt1 < 0L) {
                        frameByte.skipBytes((int)frameLength);
                    } else {
                        this.discardingTooLongFrame = true;
                        this.bytesToDiscard = frameLengthInt1;
                        frameByte.skipBytes(frameByte.readableBytes());
                    }

                    this.failIfNecessary(true);
                    return null;
                } else {
                    int frameLengthInt = (int)frameLength;
                    if(frameByte.readableBytes() < frameLengthInt) {
                        return null;
                    } else if(this.initialBytesToStrip > frameLengthInt) {
                        frameByte.skipBytes(frameLengthInt);
                        throw new CorruptedFrameException("Adjusted frame length (" + frameLength + ") is less " + "than initialBytesToStrip: " + this.initialBytesToStrip);
                    } else {
                        frameByte.skipBytes(this.initialBytesToStrip);
                        int readerIndex = frameByte.readerIndex();
                        int actualFrameLength = frameLengthInt - this.initialBytesToStrip;
                        ByteBuf frame = this.extractFrame(ctx, frameByte,readerIndex, actualFrameLength);
                        frameByte.readerIndex(readerIndex + actualFrameLength);

                        return frame;
                    }
                }
            }
        }
    }

    protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
        buf = buf.order(order);
        long frameLength;
        switch(length) {
            case 1:
                frameLength = (long)buf.getUnsignedByte(offset);
                break;
            case 2:
                long le = (long)buf.getUnsignedShort(offset);
                //序列号长度
                long snLength = 0x00FF & le;
                //报文体长度
                long bodyLength = 0x03FF & (le >> 8);
                //是否分包处理
                long isPag = 0x2000 & (le >> 8);
                if(isPag == 0x2000){
                    frameLength = 4+ snLength+bodyLength;
                }else{
                    frameLength = snLength+bodyLength;
                }
                break;
            case 3:
                frameLength = buf.getUnsignedMedium(offset);
                break;
            case 4:
                frameLength = buf.getUnsignedInt(offset);
                break;
            case 5:
            case 6:
            case 7:
            default:
                throw new DecoderException("unsupported lengthFieldLength: " + this.lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
            case 8:
                frameLength = buf.getLong(offset);
        }

        return frameLength;
    }

    private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
        if(this.bytesToDiscard == 0L) {
            long tooLongFrameLength = this.tooLongFrameLength;
            this.tooLongFrameLength = 0L;
            this.discardingTooLongFrame = false;
            if(!this.failFast || this.failFast && firstDetectionOfTooLongFrame) {
                this.fail(tooLongFrameLength);
            }
        } else if(this.failFast && firstDetectionOfTooLongFrame) {
            this.fail(this.tooLongFrameLength);
        }

    }

    protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.retainedSlice(index, length);
    }

    private void fail(long frameLength) {
        if(frameLength > 0L) {
            throw new TooLongFrameException("Adjusted frame length exceeds " + this.maxFrameLength + ": " + frameLength + " - discarded");
        } else {
            throw new TooLongFrameException("Adjusted frame length exceeds " + this.maxFrameLength + " - discarding");
        }
    }
}
